<!--
 * @Description  : 
 * @Version      : 1.0
 * @Author       : seraph
 * @Date         : 2024-12-26 12:19:10
 * @LastEditors  : seraph
 * @LastEditTime : 2025-01-20 11:31:08
-->
<template>
  <h3>声明响应式状态</h3>
  <p class="bolder">reactive()</p>
  <p>
    还有另一种声明响应式状态的方式，即使用 reactive() API。与将内部值包装在特殊对象中的 ref
    不同，reactive() 将使对象本身具有响应性：
  </p>
  <pre><code class="javascript" v-html="highlighted1"></code></pre>
  <p class="bolder">在模板中使用：</p>
  <pre><code class="html" v-html="highlighted2"></code></pre>
  <p>state is: {{ state }}<button @click="addState">state +1</button></p>
  <p>
    响应式对象是 JavaScript 代理，其行为就和普通对象一样。不同的是，Vue
    能够拦截对响应式对象所有属性的访问和修改，以便进行依赖追踪和触发更新。
  </p>
  <hr />
  <p class="bolder">reactive() 与 ref() 的区别</p>
  <p>
    ref()
    主要用于创建单个响应式属性（如数字、字符串、布尔值等基本数据类型），也可以用于定义复杂数据类型（如对象、数组等）。但其内部属性或元素不是深度响应式的，即使内部属性或元素发生变化，也不会被追踪到，除非开启了深度监听。
  </p>
  <p>
    而 reactive()
    主要用于创建一个响应式对象（对象、数组等复杂数据类型）。它能递归地将对象的所有属性都转为响应式，使得对象中的所有属性都具有响应性。可以直接访问或修改对象或数据的属性或元素，无需使用
    .value 。
  </p>
  <p>与浅层 ref 类似，这里也有一个 shallowReactive() API 可以选择退出深层响应性。</p>
  <hr />
  <h3>reactive() 的局限性</h3>
  <p>
    1、有限的值类型：它只能用于对象类型 (对象、数组和如 Map、Set 这样的集合类型)。它不能持有如
    string、number 或 boolean 这样的原始类型。
  </p>
  <p>
    2、不能替换整个对象：由于 Vue
    的响应式跟踪是通过属性访问实现的，因此我们必须始终保持对响应式对象的相同引用。这意味着我们不能轻易地“替换”响应式对象，因为这样的话与第一个引用的响应性连接将丢失：
  </p>
  <pre><code class="javascript" v-html="highlighted3"></code></pre>
  <p>
    3、对解构操作不友好：当我们将响应式对象的原始类型属性解构为本地变量时，或者将该属性传递给函数时，我们将丢失响应性连接：
  </p>
  <pre><code class="javascript" v-html="highlighted4"></code></pre>
  <p>
    state is: {{ state }} <button @click="addState">state.count +1</button> count is: {{ count }}
    <button @click="countPlusOne">count +1</button>
  </p>
  <h4 class="red">由于这些限制，我们建议使用 ref() 作为声明响应式状态的主要 API。</h4>
</template>

<script lang="ts" setup>
import { ref, reactive } from 'vue'
import hljs from 'highlight.js'
import 'highlight.js/styles/atom-one-dark.css'
import javascript from 'highlight.js/lib/languages/javascript'
import html from 'highlight.js/lib/languages/xml'

hljs.registerLanguage('javascript', javascript)
hljs.registerLanguage('html', html)

const rawCode1 = `import { reactive } from 'vue'
const state = reactive({ count: 0 })`

const highlighted1 = ref(
  hljs
    .highlight(rawCode1, { language: 'javascript' })
    .value.replace(/&amp;lt;/g, '&lt;')
    .replace(/&amp;gt;/g, '&gt;'),
)

const rawCode2 = `<span>{{ state }}</span>`

const highlighted2 = ref(
  hljs
    .highlight(rawCode2, { language: 'html' })
    .value.replace(/&amp;lt;/g, '&lt;')
    .replace(/&amp;gt;/g, '&gt;'),
)

const rawCode3 = `let state = reactive({ count: 0 })

// 上面的 ({ count: 0 }) 引用将不再被追踪
// (响应性连接已丢失！) state 现在是一个新的对象, 原来的响应性连接将被破坏。
state = reactive({ count: 1 })`

const highlighted3 = ref(
  hljs
    .highlight(rawCode3, { language: 'javascript' })
    .value.replace(/&amp;lt;/g, '&lt;')
    .replace(/&amp;gt;/g, '&gt;'),
)

const rawCode4 = `const state = reactive({ count: 0 })

// 当解构时，count 已经与 state.count 断开连接
let { count } = state
// 不会影响原始的 state
const countPlusOne = () => {
  count++;
  console.log(Date.now().toString().slice(-3), "count value is: " + count);
}

// 该函数接收到的是一个普通的数字
// 并且无法追踪 state.count 的变化
// 我们必须传入整个对象以保持响应性
callSomeFunction(state.count)`

const highlighted4 = ref(
  hljs
    .highlight(rawCode4, { language: 'javascript' })
    .value.replace(/&amp;lt;/g, '&lt;')
    .replace(/&amp;gt;/g, '&gt;'),
)

let state = reactive({ count: 0 })
state = reactive({ count: 1 })

const addState = () => {
  state.count++
}

// // 解构时丢失响应性连接, 更新了 count 变量，但不会影响原始的 state。且不会同步更新视图的 count 值。
let { count } = state
const countPlusOne = () => {
  count++
  console.log(Date.now().toString().slice(-3), 'count value is: ' + count)
}
</script>

<style scoped>
p {
  font-size: 16px;
  padding: 2px;
  margin: 5px 0;
}

/* 设置预格式化文本的样式 */
pre {
  /* 背景色 */
  background-color: #f1ffed;
  /* 边框 */
  border: 1px solid #ccc;
  /* 缩小内边距 */
  padding: 5px 5px 5px 10px;
  /* 允许滚动 */
  overflow: auto;
  /* 可选：设置上下外边距 */
  margin: 0px 0;
  /* 允许换行 */
  /* normal: 连续的空白字符会被合并为一个空格。文本会在允许的断点处自动换行。
     nowrap：连续的空白字符会被合并为一个空格。文本不会自动换行，即使在允许的断点处也不会换行。
     pre：保留所有的空白字符（包括空格、制表符和换行符）。文本只会在遇到换行符或者 <br> 标签时换行。
     pre-wrap：保留所有的空白字符。文本会在遇到换行符、<br> 标签，或者达到容器边界时换行。
     pre-line：合并连续的空白字符为一个空格，但保留换行符。文本会在遇到换行符、<br> 标签，或者达到容器边界时换行。
   */
  white-space: pre-wrap;
  /* 行高 */
  line-height: 120%;
  /* 定义缩进 */
  tab-size: 4;
}

h3,
h4,
h5,
h6 {
  margin: 25px 0 10px 0;
  line-height: 100%;
}

.bolder {
  font-weight: bolder;
}

.red {
  color: red;
}

button {
  margin: 5px 5px 5px 10px;
  line-height: 150%;
  width: auto;
  padding: 0 10px;
}
</style>
